Differenct Layer, Different Abstruction
上位レイヤは下位レイヤが提供する機能を用いる
良い設計のシステムでは、各レイヤはその上下のレイヤとは異なる抽象化を提供する
つまり、ある操作がメソッドを呼び出すことでレイヤを上下に移動するのを追跡すると、メソッドを呼び出すたびに抽象化が変わる
同じような抽象度を持つレイヤが隣接している場合、クラスの分離に問題があるサイン
単に委譲しているだけのメソッドなどradish-miyazaki.icon 「何もしない」というのが肝 radish-miyazaki.icon
https://scrapbox.io/files/6583a7f06f93490023a35cc9.png
(b) 解決策①: 下位クラスを上位クラスの呼び出し元に直接公開する
(c) 解決策②: クラス間で機能を再分配する
(d) 解決策③: クラスを分離できない場合、クラスをマージする
同じシグネチャを持つメソッドが悪いとは限らない
重要なのは、それぞれのメソッドが機能に貢献するかどうか
ディスパッチのシグネチャは、呼び出すメソッドのシグネチャと同じになることが多い
e.g. 複数の実装を持つインタフェース(OS のディスクドライバ)
各ドライバは異なる種類のディスクに対応しているが、インタフェースは同じ
同じインタフェースで異なる実装を提供する複数のメソッドがあると、認知的負荷が軽減する 1つのメソッドを使ったことがあれば、他のメソッドを使うのも簡単
デコレータオブジェクトは、基底オブジェクトと同一または類似したインタフェースを提供する
そのメソッドは基底オブジェクトのメソッドを呼び出す
ちょっとした機能を追加するだけなのに、大量の定型文を書く必要がある
e.g. Java I/O
code:java
fileInputStream fileStream = new FileInputStream(fileName);
BufferedInputStream bufferedStream = new BufferedInputStream(fileStream);
ObjectInputStream objectStream = new ObjectInputStream(bufferedStream);
新しいデコレータオブジェクトを作成する前に、以下のような代替案を検討するのが良い
基底オブジェクトに新しい機能を追加することはできないか?
以下のような条件を満たす場合に有用
機能が「ある程度」汎用的
基底オブジェクトと論理的に関連している
基底クラスの大部分でで新しい機能が使用される
e.g. Java I/O
InputStream を作成する人の殆どは BufferedInputStream も作成するので、これらのクラスは統合したほうが良い
新しい機能があるユースケースに特化している場合、それをユースケースと統合したほうが理にかなっていないか?
既存のデコレータに新しい機能を追加できないか?
基底オブジェクトとは別のクラスとして実装できないか?
クラスのインタフェースと実装は、異なる抽象度を持つべき
e.g. テキストエディタ(テキスト操作に関するモジュール)
インタフェースが行単位
高レベルの UI レイヤでは、行の途中にテキストを挿入したり、範囲選択したテキストを削除したりする必要があるので、行を分割したり結合したりする必要がある
インタフェースが文字単位
これにより、UI レイヤのコードもシンプルになる
https://scrapbox.io/files/6583c6e86fc13f0023c66089.png
場合によっては、新しい変数を追加する場合、関連するすべてのインタフェースとメソッドを変更する必要がある
解決策:
(b): 最上位と最下位のメソッド間で共有されている既存のオブジェクトが存在しないかの確認する
グローバル変数のアクセスがコンフリクトする可能性がある
筆者のおすすめ
e.g. context.Context radish-miyazaki.icon
回避策
コンテキストへの参照はシステムの大部分を占める主要なオブジェクト内にインスタンス変数として保存しておくのが良い e.g. (d) : m3 を含むクラスは、オブジェクトのインスタンス変数にコンテキストへの参照を格納する
これにより、コンテキストオブジェクトはどこからでも使えるが、顕在化するのはコンストラクタの明示的な引数としてのみ
テストもしやすくなる
コンテキストオブジェクトの変数をテスト用の値と差し替えるだけで良いradish-miyazaki.icon
デメリットを抑えるためにも、コンテキスト内の変数はイミュータブルなほうが好ましい デメリット
多くは、グローバル変数で起こるデメリットと近しい
ある変数がなぜ存在するのか、どこで使われているのかが明らかでない
ルールがないとコンテキストオブジェクトが膨大になり、不明瞭な依存関係を生み出す可能性になる hr.icon
要約
インタフェースや引数、関数、クラス、定義といったシステムを構成する各要素は、開発者がこれらの要素について学習しなければならないので、複雑さを増加させる 取り除けないならば、その要素を追加することなく実装したほうが良い
「異なるレイヤでは、異なる抽象化を持つべき」というルールは、上記の考え方の応用に過ぎない 複雑さ(費用)が著しく高い radish-miyazaki.icon その一方で、追加の機能(効果)は全く得られない